package com.maikang.exammall.api;

import com.maikang.exammall.entity.Hospital;
import com.maikang.exammall.entity.UserContext;
import com.maikang.exammall.entity.dto.*;
import com.maikang.exammall.service.HospitalService;
import com.maikang.exammall.service.RedisService;
import com.maikang.exammall.util.MapUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Example;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.util.Optional;

import static com.maikang.exammall.entity.constant.ApiCacheKeyConstant.hospitalApiKey;
import static com.maikang.exammall.entity.constant.RedisKeyConstant.getUserPositionKeyFromUserId;
import static org.apache.commons.lang3.ObjectUtils.isEmpty;
import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
import static org.apache.commons.lang3.exception.ExceptionUtils.getMessage;

@RestController
@Log4j2
@RequiredArgsConstructor
@RequestMapping("/api/hospitals")
public class HospitalController {

    final protected HospitalService service;

    final private RedisService redisService;

    @PostMapping(value = "/search")
    @Cacheable(value = hospitalApiKey, key = "'findAll:payload:'+#findAllRequest.hashCode()")
    public ResponseDTO<Iterable<Hospital>> findAll(@RequestBody FindAllRequest<Hospital> findAllRequest) {
        Iterable<Hospital> ret;
        if (findAllRequest == null || isEmpty(findAllRequest.getConditions()) && isEmpty(findAllRequest.getPageable())) {
            ret = service.findAll();
        } else if (isNotEmpty(findAllRequest.getPageable())) {
            ret = this.findAll(findAllRequest.getConditions(), findAllRequest.getPageable());
        } else {
            ret = this.findAll(Example.of(findAllRequest.getConditions()));
        }
        calculateDistances(ret);
        return ResponseDTO.ok(ret);
    }

    private PositionDTO getUserPosition() {
        Long userId = UserContext.getUserId();
        Object position = redisService.get(getUserPositionKeyFromUserId(userId));
        if (position == null) {
            log.warn("cannot get position of user {} from redis", userId);
            return null;
        }
        return (PositionDTO) position;

    }

    public void calculateDistances(Iterable<Hospital> hospitals) {
        PositionDTO position = getUserPosition();
        hospitals.forEach(hospital -> calculateDistance(hospital, position));
    }

    private void calculateDistance(Hospital hospital, PositionDTO position) {
        if (hospital == null || position == null) return;
        try {
            Double distance = MapUtil.getDistance(position.getLongitude(), position.getLatitude(),
                    hospital.getLongitude(), hospital.getLatitude());
            hospital.setDistance(distance);
        } catch (RuntimeException ignored) {

        }

    }

    public Iterable<Hospital> findAll(Hospital hospital, Page page) {
        return new SlicedContent(service.findAll(hospital, page.getPageable()));
    }

    protected Iterable<Hospital> findAll(Example<Hospital> example) {
        return service.findAll(example);

    }

    @GetMapping("/{id}")
    @Transactional(rollbackFor = Exception.class)
    @Cacheable(value = hospitalApiKey, key = "'findByIdOrNull:id:'+#id")
    public ResponseDTO<Hospital> findByIdOrNull(@PathVariable("id") Long id) {
        Hospital hospital = findById(id).orElse(null);
        PositionDTO position = getUserPosition();
        calculateDistance(hospital, position);
        return ResponseDTO.ok(hospital);
    }

    private Optional<Hospital> findById(Long id) {
        try {
            return service.findById(id);
        } catch (Exception e) {
            log.error("failed to findById: " + id, e);
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "failed to findById: " + id + ", err: " + getMessage(e));
        }
    }

}
